package com.bbex.service.impl;

import com.bbex.constant.Constants;
import com.bbex.entity.*;
import com.bbex.exception.PlatException;
import com.bbex.mapper.*;
import com.bbex.service.OfflineCoinVolumeLogService;
import com.bbex.service.PpMoveCoinVolumeService;
import com.bbex.service.UserCoinVolumeExService;
import com.bbex.util.DateUtils;
import com.bbex.util.SnowFlake;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class PpMoveCoinVolumeServiceImpl implements PpMoveCoinVolumeService {

    private Logger logger = LoggerFactory.getLogger(PpMoveCoinVolumeServiceImpl.class);

    @Autowired
    private PpMoveCoinVolumeDao moveCoinVolumeDao;

    @Autowired
    private PpMoveConfDao moveConfDao;

    @Autowired
    private UserCoinVolumeExService userCoinVolumeService;

    @Autowired
    private OfflineCoinVolumeLogService offlineCoinVolumeLogService;

    @Autowired
    private CoinDao coinDao;

    @Autowired
    private OfflineTransferLogDao offlineTransferLogDao;

    @Autowired
    private OfflineCoinVolumeDao offlineCoinVolumeDao;

    private static final int SCALE = 10; // 小数位

    @Override
    public List<PpMoveCoinVolume> findMyFund(String userId) {
        List<PpMoveConf> moveConfs = moveConfDao.findAll();
        List<PpMoveCoinVolume> moveCoinVolumes = moveConfs.stream().map(conf -> {
            PpMoveCoinVolume coinVolume = new PpMoveCoinVolume();
            coinVolume.setCoinId(conf.getCoinId());
            coinVolume.setCoinSymbol(conf.getCoinSymbol());
            coinVolume.setVolume(BigDecimal.ZERO);
            return coinVolume;
        }).collect(Collectors.toList());

        List<PpMoveCoinVolume> useCoinSymbols = moveCoinVolumeDao.findCoinFund(userId);
        if (CollectionUtils.isNotEmpty(useCoinSymbols)) {
            moveCoinVolumes.forEach(coinVol -> {
                useCoinSymbols.forEach(useVol -> {
                    if (StringUtils.equals(coinVol.getCoinSymbol(), useVol.getCoinSymbol())) {
                        coinVol.setVolume(useVol.getVolume().setScale(2, BigDecimal.ROUND_DOWN));
                    }
                });
            });
        }
        return moveCoinVolumes;
    }

    public BigDecimal getMyUsableByCoinSymbol(String userId, String coinSymbol) {
        List<PpMoveCoinVolume> moveCoinVolumes = moveCoinVolumeDao.findByCoinSymbol(coinSymbol, userId);
        if (CollectionUtils.isEmpty(moveCoinVolumes)) {
            return BigDecimal.ZERO;
        } else {
            List<PpMoveConf> moveConfs = getMoveConfs(coinSymbol);
            BigDecimal accountUsableVolume = BigDecimal.ZERO;
            for (PpMoveCoinVolume moveCoinVolume : moveCoinVolumes) {
                accountUsableVolume = accountUsableVolume.add(getUsableVolume(moveCoinVolume, moveConfs));
            }
            return accountUsableVolume.setScale(2, BigDecimal.ROUND_DOWN);
        }
    }

    private BigDecimal getUsableVolume(PpMoveCoinVolume moveCoinVolume, List<PpMoveConf> moveConfs) {
        PpMoveConf moveConf = getSuitMoveConf(moveCoinVolume, moveConfs);
        if (Objects.nonNull(moveConf)) {
            BigDecimal ratio = moveConf.getBreakRatio().add(BigDecimal.ONE);
            return moveCoinVolume.getVolume().divide(ratio, SCALE, BigDecimal.ROUND_DOWN).setScale(SCALE, BigDecimal.ROUND_DOWN);
        } else {
            return moveCoinVolume.getVolume();
        }
    }

    @Override
    public void bbOutMoveIn(String userId, String coinSymbol, BigDecimal volume) {
        logger.info("bbOutMoveIn userid:{}, coinSymbol:{}, volume:{}", userId, coinSymbol, volume);
        List<PpMoveConf> moveConfs = getMoveConfs(coinSymbol);
        BigDecimal minInVol = moveConfDao.getMinInVol();
        if (volume.compareTo(minInVol) == -1) {
            throw new PlatException(Constants.OPERRATION_ERROR, "转入搬砖账户的数量最小"+minInVol+"");
        }
        Coin coin = coinDao.findByName(coinSymbol);
        if (Objects.isNull(coin)) {
            throw new PlatException(Constants.OPERRATION_ERROR, "该币种不存在");
        }
        userCoinVolumeService.updateOutcomeException(null, volume, userId, coinSymbol);
        moveCoinVolAdd(userId, coin.getId(), coinSymbol, volume);
        saveLog(userId, coinSymbol, volume, 50);
    }

    @Transactional
    @Override
    public BigDecimal moveOutBbIn(String userId, String coinSymbol, BigDecimal volume) {
        logger.info("moveOutBbIn userid:{}, coinSymbol:{}, volume:{}", userId, coinSymbol, volume);
        List<PpMoveConf> moveConfs = getMoveConfs(coinSymbol);
        BigDecimal minOutVol = moveConfDao.getMinOutVol();
        if (volume.compareTo(minOutVol) == -1) {
            throw new PlatException(Constants.OPERRATION_ERROR, "搬砖账户转出的数量最小"+minOutVol+"");
        }
        BigDecimal breakVol = moveCoinVolSub(userId, coinSymbol, volume, moveConfs);
        userCoinVolumeService.updateIncomeException(null, volume, userId, coinSymbol);
        saveLog(userId, coinSymbol, volume, 51);
        return breakVol;
    }

    @Transactional
    @Override
    public void c2cOutMoveIn(String userId, String coinSymbol, BigDecimal volume) {
        logger.info("c2cOutMoveIn userid:{}, coinSymbol:{}, volume:{}", userId, coinSymbol, volume);
        List<PpMoveConf> moveConfs = getMoveConfs(coinSymbol);
        BigDecimal minInVol = moveConfDao.getMinInVol();
        if (volume.compareTo(minInVol) == -1) {
            throw new PlatException(Constants.OPERRATION_ERROR, "转入搬砖账户的数量最小"+minInVol+"");
        }
        Coin coin = coinDao.findByName(coinSymbol);
        if (Objects.isNull(coin)) {
            throw new PlatException(Constants.OPERRATION_ERROR, "该币种不存在");
        }
        c2cCoinVolumeSubtract(userId, coin.getId(), volume, SnowFlake.createSnowFlake().nextIdString());
        moveCoinVolAdd(userId, coin.getId(), coinSymbol, volume);
        saveLog(userId, coinSymbol, volume, 52);
    }

    @Transactional
    @Override
    public BigDecimal moveOutC2cIn(String userId, String coinSymbol, BigDecimal volume) {
        logger.info("moveOutC2cIn userid:{}, coinSymbol:{}, volume:{}", userId, coinSymbol, volume);
        List<PpMoveConf> moveConfs = getMoveConfs(coinSymbol);
        BigDecimal minOutVol = moveConfDao.getMinOutVol();
        if (volume.compareTo(minOutVol) == -1) {
            throw new PlatException(Constants.OPERRATION_ERROR, "搬砖账户转出的数量最小"+minOutVol+"");
        }
        Coin coin = coinDao.findByName(coinSymbol);
        if (Objects.isNull(coin)) {
            throw new PlatException(Constants.OPERRATION_ERROR, "该币种不存在");
        }
        BigDecimal breakVol = moveCoinVolSub(userId, coinSymbol, volume, moveConfs);
        c2cCoinVolumeAdd(userId, coin.getId(), coinSymbol, volume, SnowFlake.createSnowFlake().nextIdString());
        saveLog(userId, coinSymbol, volume, 53);
        return breakVol;
    }


    /**
     * 搬砖账户扣减资产
     * @param userId
     * @param coinSymbol
     * @param volume
     * @param moveConfs
     * @return
     */
    private BigDecimal moveCoinVolSub(String userId, String coinSymbol, BigDecimal volume, List<PpMoveConf> moveConfs) {
        logger.info("moveCoinVolSub userid:{}, coinSymbol:{}, volume:{}", userId, coinSymbol, volume);
        List<PpMoveCoinVolume> moveCoinVolumes = moveCoinVolumeDao.findByCoinSymbol(coinSymbol, userId);
        BigDecimal sumFund = BigDecimal.ZERO;
        if (CollectionUtils.isNotEmpty(moveCoinVolumes)) {
            for (PpMoveCoinVolume moveCoinVolume : moveCoinVolumes) {
                sumFund = sumFund.add(moveCoinVolume.getVolume());
            }
        }
        if (sumFund.compareTo(volume) == -1) {
            throw new PlatException(Constants.OPERRATION_ERROR, "搬砖账户余额不足");
        }
        BigDecimal lastVol = volume; // 搬砖账户扣减数量
        List<PpMoveCoinVolume> updateMoveVoinVolumes = new ArrayList<>(); // 需要更新的搬砖记录
        PpMoveConf moveConf = null; // 对应的取款违约配置
        BigDecimal breakVolume = BigDecimal.ZERO; // 违约金
        BigDecimal sumBreakVol = BigDecimal.ZERO; // 本次扣减总违约金
        for (PpMoveCoinVolume moveCoinVolume : moveCoinVolumes) {
            if (lastVol.compareTo(BigDecimal.ZERO) == 0) {
                break;
            }
            moveConf = getSuitMoveConf(moveCoinVolume, moveConfs); // 获取合适的违约金配置
            if (Objects.nonNull(moveConf)) {
                breakVolume = moveConf.getBreakRatio().multiply(lastVol).setScale(SCALE, BigDecimal.ROUND_DOWN);
            } else {
                breakVolume = BigDecimal.ZERO;
            }
            // 如果该笔搬砖可用数量够扣减，则直接从余额中扣减
            if (moveCoinVolume.getVolume().subtract(lastVol).subtract(breakVolume).compareTo(BigDecimal.ZERO) >= 0) {
                // 扣减
                moveCoinVolume.setVolume(moveCoinVolume.getVolume().subtract(lastVol).subtract(breakVolume));
                moveCoinVolume.setBreakVolume(moveCoinVolume.getBreakVolume().add(breakVolume));
                // 设置余量
                lastVol = BigDecimal.ZERO;
                sumBreakVol = sumBreakVol.add(breakVolume);
            } else {
                if (Objects.nonNull(moveConf)) {
                    BigDecimal ratio = moveConf.getBreakRatio().add(BigDecimal.ONE);
                    breakVolume = moveCoinVolume.getVolume().subtract(moveCoinVolume.getVolume().divide(ratio, SCALE, BigDecimal.ROUND_DOWN)).setScale(SCALE, BigDecimal.ROUND_DOWN);
                } else {
                    breakVolume = BigDecimal.ZERO;
                }
                // 设置余量
                lastVol = lastVol.subtract(moveCoinVolume.getVolume()).add(breakVolume);
                sumBreakVol = sumBreakVol.add(breakVolume);
                // 如果该笔搬砖不够扣减，则该笔搬砖可用全部扣除
                moveCoinVolume.setVolume(BigDecimal.ZERO);
                moveCoinVolume.setBreakVolume(moveCoinVolume.getBreakVolume().add(breakVolume));
            }
            updateMoveVoinVolumes.add(moveCoinVolume);// 将该笔搬砖记录加入到更新搬砖集合中
        }
        if (lastVol.compareTo(BigDecimal.ZERO) != 0) {
            throw new PlatException(Constants.OPERRATION_ERROR, "搬砖账户余额不足");
        }
        // 更新搬砖账户资产
        long count = 0;
        for (PpMoveCoinVolume moveCoinVolume : updateMoveVoinVolumes) {
            if (moveCoinVolume.getVolume().compareTo(BigDecimal.ZERO) == -1) {
                throw new PlatException(Constants.OPERRATION_ERROR, "更新失败");
            }
            count = moveCoinVolumeDao.updateVolume(moveCoinVolume);
            if (count != 1) {
                throw new PlatException(Constants.OPERRATION_ERROR, "更新失败");
            }
        }
        return sumBreakVol;
    }

    private PpMoveConf getSuitMoveConf(PpMoveCoinVolume moveCoinVolume, List<PpMoveConf> moveConfs) {
        long day = DateUtils.getDaysBetween(moveCoinVolume.getCreateDate().toLocalDate(), LocalDate.now());
        for (PpMoveConf moveConf : moveConfs) {
            if (moveConf.getLockDay() > day) {
                return moveConf;
            }
        }
        return null;
    }

    /**
     * 搬砖账户加资产
     * @param userId
     * @param coinSymbol
     * @param volume
     */
    private void moveCoinVolAdd(String userId, String coinId, String coinSymbol, BigDecimal volume) {

        PpMoveCoinVolume moveCoinVolume = new PpMoveCoinVolume();
        moveCoinVolume.setId(SnowFlake.createSnowFlake().nextIdString());
        moveCoinVolume.setCoinId(coinId);
        moveCoinVolume.setCoinSymbol(coinSymbol);
        moveCoinVolume.setInVolume(volume);
        moveCoinVolume.setVolume(volume);
        moveCoinVolume.setBreakVolume(BigDecimal.ZERO);
        moveCoinVolume.setUserId(userId);
        moveCoinVolume.setCreateDate(LocalDateTime.now());
        moveCoinVolume.setVersion(1);
        moveCoinVolumeDao.insert(moveCoinVolume);
    }

    private List<PpMoveConf> getMoveConfs(String coinSymbol) {
        List<PpMoveConf> moveConfs = moveConfDao.findByCoinSymbol(coinSymbol);
        if (CollectionUtils.isEmpty(moveConfs)) {
            throw new PlatException(Constants.OPERRATION_ERROR, "该币种搬砖规则未设定，限制转入转出！");
        }
        return moveConfs;
    }

    /**
     * @param userId
     * @param coinSymbol
     * @param volume
     * @param type
     * @return
     */
    private OfflineTransferLog saveLog(String userId, String coinSymbol, BigDecimal volume, Integer type) {
        OfflineTransferLog offlineTransferLog = new OfflineTransferLog();
        String id = SnowFlake.createSnowFlake().nextIdString();
        offlineTransferLog.setId(id);
        offlineTransferLog.setCreateDate(LocalDateTime.now());
        offlineTransferLog.setUpdateDate(LocalDateTime.now());
        offlineTransferLog.setUserId(userId);
        offlineTransferLog.setCoinSymbol(coinSymbol);
        offlineTransferLog.setVolume(volume);
        offlineTransferLog.setType(type);
        Coin coin = coinDao.findByName(coinSymbol);
        offlineTransferLog.setCoinId(Objects.nonNull(coin) ? coin.getId() : "");
        offlineTransferLog.setSourceVolume(BigDecimal.ZERO);
        offlineTransferLogDao.insert(offlineTransferLog);
        return offlineTransferLog;
    }

    public void c2cCoinVolumeAdd(String userId, String coinId, String symbol, BigDecimal addVolume, String batchNo) {
        if (addVolume.compareTo(BigDecimal.ZERO) < 1) {
            throw new PlatException(Constants.PARAM_ERROR, "需要转入的资产数量错误");
        }
        OfflineCoinVolume offlineCoinVolume = offlineCoinVolumeDao.findByUserIdAndCoinId(userId, coinId);
        if (Objects.isNull(offlineCoinVolume)) {
            offlineCoinVolume = new OfflineCoinVolume();
            offlineCoinVolume.setId(SnowFlake.createSnowFlake().nextIdString());
            offlineCoinVolume.setCoinId(coinId);
            offlineCoinVolume.setCoinSymbol(symbol);
            offlineCoinVolume.setUserId(userId);
            offlineCoinVolume.setVolume(addVolume);
            offlineCoinVolume.setAdvertVolume(BigDecimal.ZERO);
            offlineCoinVolume.setLockVolume(BigDecimal.ZERO);
            offlineCoinVolume.setBailVolume(BigDecimal.ZERO);
            offlineCoinVolume.setCreateDate(LocalDateTime.now());
            offlineCoinVolume.setUpdateDate(LocalDateTime.now());
            offlineCoinVolume.setVersion(0L);
            offlineCoinVolumeDao.insert(offlineCoinVolume);
            offlineCoinVolumeLogService.saveLog(offlineCoinVolume.getUserId(), offlineCoinVolume.getCoinId(), batchNo);   //资产变更记录日志
        } else {
            offlineCoinVolumeLogService.saveLog(offlineCoinVolume, batchNo);// C2C资产流水
            offlineCoinVolume.setVolume(offlineCoinVolume.getVolume().add(addVolume));
            long count = offlineCoinVolumeDao.updateCoinVolumeInfo(offlineCoinVolume);
            if (count != 1) {
                throw new PlatException(Constants.UPDATE_ERROR, "更新失败");
            }
            offlineCoinVolumeLogService.saveLog(userId, coinId, batchNo);// C2C资产流水
        }
    }

    public void c2cCoinVolumeSubtract(String userId, String coinId, BigDecimal subtractVolume, String batchNo) {
        if (subtractVolume.compareTo(BigDecimal.ZERO) < 1) {
            throw new PlatException(Constants.PARAM_ERROR, "需要转出的资产数量错误");
        }
        OfflineCoinVolume offlineCoinVolume = offlineCoinVolumeDao.findByUserIdAndCoinId(userId, coinId);
        if (Objects.isNull(offlineCoinVolume)) {
            throw new PlatException(Constants.PARAM_ERROR, "用户资产不存在");
        }
        BigDecimal volume = offlineCoinVolume.getVolume().subtract(subtractVolume);
        if (volume.compareTo(BigDecimal.ZERO) == -1) {
            throw new PlatException(Constants.PARAM_ERROR, "用户资产不足");
        }
        offlineCoinVolumeLogService.saveLog(offlineCoinVolume, batchNo);// C2C资产流水
        offlineCoinVolume.setVolume(volume);
        long count = offlineCoinVolumeDao.updateCoinVolumeInfo(offlineCoinVolume);
        if (count != 1) {
            throw new PlatException(Constants.UPDATE_ERROR, "更新失败");
        }
        offlineCoinVolumeLogService.saveLog(userId, coinId, batchNo);// C2C资产流水
    }
}
